﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace ThresholdAssistant.Tools
{
    /// <summary>
    /// RSA机密工具类
    /// </summary>
    public class RSAFromPkcs8Util
    {
        //linux 安装openssl命令
        //yum install -y openssl

        //私钥生成
        //openssl genrsa -out private_pkcs1.pem 1024
        //openssl pkcs8 -topk8 -inform PEM -in private_pkcs1.pem -outform pem -nocrypt -out private_pkcs8.pem

        //公钥生成
        //openssl rsa -in private_pkcs1.pem -pubout -out public_pkcs8.pem


        //调用实例
        //生成签名
        //var paramSortDir = new SortedDictionary<string, object>() { { "Id", 1 }, { "Name", "测试" } };
        //var signText = RSAFromPkcs8Util.Sign(paramSortDir, privateKey);

        ////验证签名
        //var isSuccess = RSAFromPkcs8Util.CheckSign(paramSortDir, signText, publicKey);

        ////加解密测试
        //var text = RSAFromPkcs8Util.PublicEncrypt("测试", publicKey);//公钥加密
        //var content1 = RSAFromPkcs8Util.PrivateDecrypt(text, privateKey);//私钥解密

        /// <summary>
        /// 将参数按照a-z进行排序，然后利用rsa生成签名
        /// </summary>
        /// <param name="paramSortDir">参数排序字典(从字母a-z)</param>
        /// <param name="signStrings">签名字段</param>
        /// <param name="publicKey">公钥</param>
        /// <returns></returns>
        public static bool CheckSign(SortedDictionary<string, object> paramSortDir, string signStrings, string publicKey)
        {
            StringBuilder paramBuilder = new StringBuilder();
            foreach (KeyValuePair<string, object> param in paramSortDir)
            {
                paramBuilder.Append(param.Value);
            }
            return CheckSign(paramBuilder.ToString(), signStrings, publicKey);
        }

        /// <summary>
        /// 验签
        /// </summary>
        /// <param name="content">待验签字符串</param>
        /// <param name="signStrings">签名</param>
        /// <param name="publicKey">公钥</param>
        /// <returns>true(通过)，false(不通过)</returns>
        public static bool CheckSign(string content, string signStrings, string publicKey)
        {
            byte[] dataBytes = Encoding.UTF8.GetBytes(content);
            byte[] data = System.Convert.FromBase64String(signStrings);
            RSAParameters paraPub = ConvertFromPublicKey(publicKey);
            RSACryptoServiceProvider rsaPub = new RSACryptoServiceProvider();
            rsaPub.ImportParameters(paraPub);
            SHA1 sh = new SHA1CryptoServiceProvider();
            bool result = rsaPub.VerifyData(dataBytes, sh, data);
            return result;
        }

        /// <summary>
        /// 将参数按照a-z进行排序，然后利用rsa生成签名
        /// </summary>
        /// <param name="paramSortDir">参数排序字典(从字母a-z)</param>
        /// <param name="privateKey">私钥</param>
        /// <returns></returns>
        public static string Sign(SortedDictionary<string, object> paramSortDir, string privateKey)
        {
            StringBuilder paramBuilder = new StringBuilder();
            foreach (KeyValuePair<string, object> param in paramSortDir)
            {
                paramBuilder.Append(param.Value);
            }
            //生成签名
            return Sign(paramBuilder.ToString(), privateKey);
        }

        /// <summary>
        /// 利用RSA私钥生成签名字符串
        /// </summary>
        /// <param name="content">待签名字符串</param>
        /// <param name="privateKey">私钥</param>
        /// <returns>签名后字符串</returns>
        public static string Sign(string content, string privateKey)
        {
            byte[] dataBytes = Encoding.UTF8.GetBytes(content);
            RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey);
            SHA1 sh = new SHA1CryptoServiceProvider();
            byte[] signData = rsa.SignData(dataBytes, sh);
            return System.Convert.ToBase64String(signData);
        }

        /// <summary>
        /// 使用公钥进行加密
        /// </summary>
        /// <param name="content">需要加密的字符串</param>
        /// <param name="publicKey">公钥</param>
        /// <returns></returns>
        public static string PublicEncrypt(string content, string publicKey)
        {
            byte[] dataToEncryptBytes = Encoding.UTF8.GetBytes(content);
            RSACryptoServiceProvider rsa = DecodePemPublicKey(publicKey);
            byte[] encryptBytes = rsa.Encrypt(dataToEncryptBytes, false);
            return System.Convert.ToBase64String(encryptBytes);
        }

        /// <summary>
        /// 私钥解密
        /// </summary>
        /// <param name="content">加密字符串</param>
        /// <param name="privateKey">私钥</param>
        /// <returns>明文</returns>
        public static string PrivateDecrypt(string content, string privateKey)
        {
            byte[] dataBytes = System.Convert.FromBase64String(content);
            string result = "";
            for (int j = 0; j < dataBytes.Length / 128; j++)
            {
                byte[] buf = new byte[128];
                for (int i = 0; i < 128; i++)
                {

                    buf[i] = dataBytes[i + 128 * j];
                }
                result += PrivateDecrypt(buf, privateKey);
            }
            return result;
        }

        /// <summary>
        /// 私钥解密
        /// </summary>
        /// <param name="data"></param>
        /// <param name="privateKey"></param>
        /// <returns></returns>
        private static string PrivateDecrypt(byte[] data, string privateKey)
        {
            string input_charset = "UTF-8";
            RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey);
            SHA1 sh = new SHA1CryptoServiceProvider();
            byte[] source = rsa.Decrypt(data, false);
            char[] asciiChars = new char[Encoding.GetEncoding(input_charset).GetCharCount(source, 0, source.Length)];
            Encoding.GetEncoding(input_charset).GetChars(source, 0, source.Length, asciiChars, 0);
            return new string(asciiChars);
        }

        /// <summary>
        /// 解码RSA公钥
        /// </summary>
        /// <param name="publicKey">公钥</param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodePemPublicKey(string publicKey)
        {
            RSACryptoServiceProvider provider = null;
            byte[] pkcs8PublickKeyBytes = System.Convert.FromBase64String(publicKey);
            if (pkcs8PublickKeyBytes != null)
            {
                provider = DecodeRSAPublicKey(pkcs8PublickKeyBytes);
            }
            return provider;
        }

        /// <summary>
        /// 解码RSA私钥
        /// </summary>
        /// <param name="privateKey">私钥</param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodePemPrivateKey(string privateKey)
        {
            byte[] privateKeyBytes = System.Convert.FromBase64String(privateKey);
            MemoryStream mem = new MemoryStream(privateKeyBytes);
            int lenstream = (int)mem.Length;
            BinaryReader binr = new BinaryReader(mem);//wrap Memory Stream with BinaryReader for easy reading
            try
            {
                byte[] seqOId = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
                ushort twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130)	//data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();	//advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();	//advance 2 bytes
                else
                    return null;

                byte bt = binr.ReadByte();
                if (bt != 0x02)
                    return null;

                twobytes = binr.ReadUInt16();

                if (twobytes != 0x0001)
                    return null;

                byte[] seq = new byte[15];
                seq = binr.ReadBytes(15);//read the Sequence OID
                if (!CompareBytearrays(seq, seqOId))	//make sure Sequence for OID is correct
                    return null;

                bt = binr.ReadByte();
                if (bt != 0x04)	//expect an Octet string 
                    return null;

                bt = binr.ReadByte();		//read next byte, or next 2 bytes is  0x81 or 0x82; otherwise bt is the byte count
                if (bt == 0x81)
                    binr.ReadByte();
                else
                    if (bt == 0x82)
                    binr.ReadUInt16();

                //------ at this stage, the remaining sequence should be the RSA private key
                byte[] rsaPrivkeyBytes = binr.ReadBytes((int)(lenstream - mem.Position));
                RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaPrivkeyBytes);
                return rsacsp;
            }

            catch (Exception)
            {
                return null;
            }

            finally
            {
                binr.Close();
            }
        }

        /// <summary>
        /// 解码私钥
        /// </summary>
        /// <param name="privkeyBytes"></param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkeyBytes)
        {
            // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
            MemoryStream mem = new MemoryStream(privkeyBytes);
            BinaryReader binr = new BinaryReader(mem);//wrap Memory Stream with BinaryReader for easy reading
            try
            {
                ushort twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130)	//data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();	//advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();	//advance 2 bytes
                else
                    return null;

                twobytes = binr.ReadUInt16();
                if (twobytes != 0x0102)	//version number
                    return null;
                byte bt = binr.ReadByte();
                if (bt != 0x00)
                    return null;

                byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
                //------  all private key components are Integer sequences ----
                int elems = GetIntegerSize(binr);
                MODULUS = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                E = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                D = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                P = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                Q = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                DP = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                DQ = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                IQ = binr.ReadBytes(elems);

                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                RSAParameters RSAparams = new RSAParameters();
                RSAparams.Modulus = MODULUS;
                RSAparams.Exponent = E;
                RSAparams.D = D;
                RSAparams.P = P;
                RSAparams.Q = Q;
                RSAparams.DP = DP;
                RSAparams.DQ = DQ;
                RSAparams.InverseQ = IQ;
                RSA.ImportParameters(RSAparams);
                return RSA;
            }
            catch (Exception)
            {
                return null;
            }
            finally { binr.Close(); }
        }

        /// <summary>
        /// 解码RSA公钥
        /// </summary>
        /// <param name="pkcs8publickKeyBytes"></param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodeRSAPublicKey(byte[] pkcs8publickKeyBytes)
        {
            // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
            MemoryStream mem = new MemoryStream(pkcs8publickKeyBytes);
            BinaryReader binr = new BinaryReader(mem);//wrap Memory Stream with BinaryReader for easy reading
            try
            {
                // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
                byte[] seqOId = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
                byte[] seq = new byte[15];

                ushort twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();    //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();   //advance 2 bytes
                else
                    return null;

                seq = binr.ReadBytes(15);       //read the Sequence OID
                if (!CompareBytearrays(seq, seqOId))    //make sure Sequence for OID is correct
                    return null;

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                    binr.ReadByte();    //advance 1 byte
                else if (twobytes == 0x8203)
                    binr.ReadInt16();   //advance 2 bytes
                else
                    return null;

                byte bt = binr.ReadByte();
                if (bt != 0x00)     //expect null byte next
                    return null;

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();    //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();   //advance 2 bytes
                else
                    return null;

                twobytes = binr.ReadUInt16();
                byte lowbyte = 0x00;
                byte highbyte = 0x00;

                //data read as little endian order (actual data order for Integer is 02 81)
                if (twobytes == 0x8102)
                {
                    // read next bytes which is bytes in modulus
                    lowbyte = binr.ReadByte();
                }
                else if (twobytes == 0x8202)
                {
                    //advance 2 bytes
                    highbyte = binr.ReadByte();
                    lowbyte = binr.ReadByte();
                }
                else
                {
                    return null;
                }

                //reverse byte order since asn.1 key uses big endian order
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                int modsize = BitConverter.ToInt32(modint, 0);

                byte firstbyte = binr.ReadByte();
                binr.BaseStream.Seek(-1, SeekOrigin.Current);

                if (firstbyte == 0x00)
                {
                    //if first byte (highest order) of modulus is zero, don't include it
                    binr.ReadByte();//skip this null byte
                    modsize -= 1;   //reduce modulus buffer size by 1
                }

                byte[] modulus = binr.ReadBytes(modsize);//read the modulus bytes
                if (binr.ReadByte() != 0x02)//expect an Integer for the exponent data
                    return null;

                int expbytes = binr.ReadByte();
                byte[] exponent = binr.ReadBytes(expbytes);// should only need one byte for actual exponent data (for all useful values)

                // -------create RSACryptoServiceProvider instance and initialize with public key -----
                RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
                RSAParameters rsaKeyInfo = new RSAParameters
                {
                    Modulus = modulus,
                    Exponent = exponent
                };
                rsaProvider.ImportParameters(rsaKeyInfo);
                return rsaProvider;
            }
            catch (Exception)
            {
                return null;
            }
            finally
            {
                binr.Close();
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static bool CompareBytearrays(byte[] a, byte[] b)
        {
            if (a.Length != b.Length)
                return false;

            int i = 0;
            foreach (byte c in a)
            {
                if (c != b[i])
                    return false;
                i++;
            }
            return true;
        }

        /// <summary>
        /// 获取整数的大小
        /// </summary>
        /// <param name="binr"></param>
        /// <returns></returns>
        private static int GetIntegerSize(BinaryReader binr)
        {
            byte bt = binr.ReadByte();
            if (bt != 0x02)//expect integer
                return 0;

            bt = binr.ReadByte();
            int count;
            if (bt == 0x81)
            {
                // data size in next byte
                count = binr.ReadByte();
            }
            else
            {
                if (bt == 0x82)
                {
                    // data size in next 2 bytes
                    byte highbyte = binr.ReadByte();
                    byte lowbyte = binr.ReadByte();
                    byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                    count = BitConverter.ToInt32(modint, 0);
                }
                else
                {
                    // we already have the data size
                    count = bt;
                }
            }
            while (binr.ReadByte() == 0x00)
            {
                //remove high order zeros in data
                count -= 1;
            }

            //last ReadByte wasn't a removed zero, so back up a byte
            binr.BaseStream.Seek(-1, SeekOrigin.Current);
            return count;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="publicKey">公钥字符串</param>
        /// <returns></returns>
        private static RSAParameters ConvertFromPublicKey(string publicKey)
        {

            byte[] keyData = System.Convert.FromBase64String(publicKey);
            if (keyData.Length < 162)
            {
                throw new ArgumentException("pem file content is incorrect.");
            }
            byte[] pemModulus = new byte[128];
            byte[] pemPublicExponent = new byte[3];
            Array.Copy(keyData, 29, pemModulus, 0, 128);
            Array.Copy(keyData, 159, pemPublicExponent, 0, 3);
            RSAParameters para = new RSAParameters();
            para.Modulus = pemModulus;
            para.Exponent = pemPublicExponent;
            return para;
        }
    }
}